/* eslint-disable no-redeclare */
'use strict'

var css = require('css')
var extend = require('extend')

var defaultConfig = {
  baseDpr: 2, // base device pixel ratio (default: 2)
  remUnit: 192, // rem unit value (default: 75)
  remPrecision: 6, // rem value precision (default: 6)
  forcePxComment: 'px', // force px comment (default: `px`)
  keepComment: 'no' // no transform value comment (default: `no`)
}

var pxRegExp = /\b(\d+(\.\d+)?)px\b/

function Px2rem(options) {
  this.config = {}
  extend(this.config, defaultConfig, options)
}

// generate @1x, @2x and @3x version stylesheet
Px2rem.prototype.generateThree = function (cssText, dpr) {
  dpr = dpr || 2
  var self = this
  var config = self.config
  var astObj = css.parse(cssText)

  function processRules(rules) {
    for (var i = 0; i < rules.length; i++) {
      var rule = rules[i]
      if (rule.type === 'media') {
        processRules(rule.rules) // recursive invocation while dealing with media queries
        continue
      } else if (rule.type === 'keyframes') {
        processRules(rule.keyframes) // recursive invocation while dealing with keyframes
        continue
      } else if (rule.type !== 'rule' && rule.type !== 'keyframe') {
        continue
      }

      var declarations = rule.declarations
      for (var j = 0; j < declarations.length; j++) {
        var declaration = declarations[j]
        // need transform: declaration && has 'px'
        if (declaration.type === 'declaration' && pxRegExp.test(declaration.value)) {
          var nextDeclaration = rule.declarations[j + 1]
          if (nextDeclaration && nextDeclaration.type === 'comment') { // next next declaration is comment
            if (nextDeclaration.comment.trim() === config.keepComment) { // no transform
              declarations.splice(j + 1, 1) // delete corresponding comment
              continue
            } else if (nextDeclaration.comment.trim() === config.forcePxComment) { // force px
              declarations.splice(j + 1, 1) // delete corresponding comment
            }
          }
          declaration.value = self._getCalcValue('px', declaration.value, dpr) // common transform
        }
      }
    }
  }

  processRules(astObj.stylesheet.rules)

  return css.stringify(astObj)
}

// generate rem version stylesheet
Px2rem.prototype.generateRem = function (cssText) {
  var self = this
  var config = self.config
  var astObj = css.parse(cssText)

  function processRules(rules, noDealPx) { // FIXME: keyframes do not support `force px` comment
    for (var i = 0; i < rules.length; i++) {
      var rule = rules[i]
      if (rule.type === 'media') {
        processRules(rule.rules) // recursive invocation while dealing with media queries
        continue
      } else if (rule.type === 'keyframes') {
        processRules(rule.keyframes, true) // recursive invocation while dealing with keyframes
        continue
      } else if (rule.type !== 'rule' && rule.type !== 'keyframe') {
        continue
      }

      if (!noDealPx) {
        // generate 3 new rules which has [data-dpr]
        var newRules = []
        for (var dpr = 1; dpr <= 3; dpr++) {
          var newRule = {}
          newRule.type = rule.type
          newRule.selectors = rule.selectors.map(function (sel) {
            return '[data-dpr="' + dpr + '"] ' + sel
          })
          newRule.declarations = []
          newRules.push(newRule)
        }
      }

      var declarations = rule.declarations
      for (var j = 0; j < declarations.length; j++) {
        var declaration = declarations[j]
        // need transform: declaration && has 'px'
        if (declaration.type === 'declaration' && pxRegExp.test(declaration.value)) {
          var nextDeclaration = rule.declarations[j + 1]
          if (nextDeclaration && nextDeclaration.type === 'comment') { // next next declaration is comment
            if (nextDeclaration.comment.trim() === config.forcePxComment) { // force px
              // do not transform `0px`
              if (declaration.value === '0px') {
                declaration.value = '0'
                declarations.splice(j + 1, 1) // delete corresponding comment
                continue
              }
              if (!noDealPx) {
                // generate 3 new declarations and put them in the new rules which has [data-dpr]
                for (var dpr = 1; dpr <= 3; dpr++) {
                  var newDeclaration = {}
                  extend(true, newDeclaration, declaration)
                  newDeclaration.value = self._getCalcValue('px', newDeclaration.value, dpr)
                  newRules[dpr - 1].declarations.push(newDeclaration)
                }
                declarations.splice(j, 2) // delete this rule and corresponding comment
                j--
              } else { // FIXME: keyframes do not support `force px` comment
                declaration.value = self._getCalcValue('rem', declaration.value) // common transform
                declarations.splice(j + 1, 1) // delete corresponding comment
              }
            } else if (nextDeclaration.comment.trim() === config.keepComment) { // no transform
              declarations.splice(j + 1, 1) // delete corresponding comment
            } else {
              declaration.value = self._getCalcValue('rem', declaration.value) // common transform
            }
          } else {
            declaration.value = self._getCalcValue('rem', declaration.value) // common transform
          }
        }
      }

      // if the origin rule has no declarations, delete it
      if (!rules[i].declarations.length) {
        rules.splice(i, 1)
        i--
      }

      if (!noDealPx) {
        // add the new rules which contain declarations that are forced to use px
        if (newRules[0].declarations.length) {
          rules.splice(i + 1, 0, newRules[0], newRules[1], newRules[2])
          i += 3 // skip the added new rules
        }
      }
    }
  }

  processRules(astObj.stylesheet.rules)

  return css.stringify(astObj)
}

// get calculated value of px or rem
Px2rem.prototype._getCalcValue = function (type, value, dpr) {
  var config = this.config
  var pxGlobalRegExp = new RegExp(pxRegExp.source, 'g')

  function getValue(val) {
    val = parseFloat(val.toFixed(config.remPrecision)) // control decimal precision of the calculated value
    return val === 0 ? val : val + type
  }

  return value.replace(pxGlobalRegExp, function ($0, $1) {
    return type === 'px' ? getValue($1 * dpr / config.baseDpr) : getValue($1 / config.remUnit)
  })
}

module.exports = Px2rem
